﻿//--------------------------------------------------
// TXna Game Engine
// This file belongs to the TXnaGE
// https://sourceforge.net/projects/txnagameengine/
//--------------------------------------------------

using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using sd = System.Drawing;
using System;
using RainEngine.Controllers;
using RainEngine.SceneNodes;

namespace SceneEditor
{
    public delegate void CameraMoved();

    /// <summary>
    /// We need a camera controller which lets the WinForm user move the camera with the keyboard but also rotate it using the right mouse button.
    /// </summary>
    public class CameraController : Controller
    {
        #region 构造函数和成员变量

        /// <summary>
        /// 该控制器控制的相机
        /// </summary>
        private CameraSceneNode             camera;
        
        private MouseState                  originalMouseState;
        //private InputBinding                input;

        private System.Windows.Forms.Panel  panel;
        private System.Windows.Forms.Form   form;

        private float                       movementFactor = 4;
        private bool                        isFirstTime = true;

        private Quaternion yaw = Quaternion.Identity, pitch = Quaternion.Identity;

        /// <summary>
        /// Creates a new FreeCameraController instance.
        /// </summary>
        /// <param name="engine"></param>
        public CameraController(SceneEditor engine, System.Windows.Forms.Panel panel): base(engine)
        {
            this.panel = panel;
            this.form = (System.Windows.Forms.Form)panel.TopLevelControl;
            this.originalMouseState = new MouseState(engine.GraphicsDevice.Viewport .Width / 2, engine.GraphicsDevice.Viewport .Height / 2, 0, ButtonState.Released, ButtonState.Released, ButtonState.Released, ButtonState.Released, ButtonState.Released);
            /*input = new InputBinding();
            input.AddBinding(new KeyBinding(Keys.W, "forward"));
            input.AddBinding(new KeyBinding(Keys.S, "back"));
            input.AddBinding(new KeyBinding(Keys.A, "left"));
            input.AddBinding(new KeyBinding(Keys.D, "right"));
            input.AddBinding(new KeyBinding(Keys.LeftShift, "run"));
            input.AddBinding(new KeyBinding(Keys.R, "up"));
            input.AddBinding(new KeyBinding(Keys.F, "down"));*/
        }
        #endregion

        public event CameraMoved CameraMoved;

        /// <summary>
        /// Rotates the camera so that it faces the given target.
        /// </summary>
        /// <param name="target"></param>
        public void LookAt(Vector3 target)
        {
            yaw = Quaternion.Identity;
            pitch = Quaternion.Identity;

            //  get yaw
            Vector3 position = camera.Pose.Position;
            position.Y = target.Y;  //  now there will be no pitch
            yaw = Quaternion.CreateFromRotationMatrix(Matrix.CreateBillboard(position, target, Vector3.Up, Vector3.Forward));
            
            //  now get the pitch
            float adjacent = (position - target).Length();
            float opposite = target.Y - camera.Pose.Position.Y;
            float hypotenuse = (float) Math.Sqrt((adjacent * adjacent) + (opposite * opposite));
            float sineTheta = opposite / hypotenuse;
            float angleTheta = (float) Math.Asin(sineTheta);
            pitch = Quaternion.CreateFromRotationMatrix(Matrix.CreateFromAxisAngle(Vector3.Right, angleTheta));

            Quaternion finalRot = yaw * pitch;
            camera.Pose.SetRotation(ref finalRot);
            camera.UpdateViewMatrix(true);
            RaiseCameraMoved();
        }
        /// <summary>
        /// Rotates the camera so that it faces the given scene node.
        /// </summary>
        /// <param name="node"></param>
        public void LookAt(Renderable3DSceneNode node)
        {           
            LookAt(node.Pose.GeometryCenter);
        }
        /// <summary>
        /// The main controller logic implementation.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void ControllerAction(GameTime gameTime)
        {
            if (!panel.ContainsFocus || form.OwnedForms.Length>0)
                return;

            
            //We controll only cameras so let the exception just propagate on invalid cast exception.
            camera = (CameraSceneNode)controlledNode;

            if (isFirstTime)
            {
                LookAt(camera.Target );
                isFirstTime = false;
            }
            float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
            ProcessInput(timeDifference);
        }

        /// <summary>
        /// Gets the kayboard bindings for the camera movements.
        /// </summary>
        //public InputBinding InputBinding { get { return input; } }

        /// <summary>
        /// Gets or sets the cameras movement speed multiplier if the "run" key is pressed.
        /// </summary>
        public float MovementFactor
        {
            get { return movementFactor; }
            set { movementFactor = value; }
        }

        private void ProcessInput(float ellapsedTime)
        {
            float cameraPitch = 0;
            float cameraYaw = 0;
            float rotationPerSecond = ellapsedTime * MathHelper.ToRadians(camera.RotationSpeed);
            float movementPerSecond = ellapsedTime * camera.MovementSpeed;
            
            
            //KeyboardState keyState = engine.KeyBoardState;


            if (engine.IsActive)
            {
                MouseState currentMouseState = Mouse.GetState();
                //sd.Rectangle client = engine.Control.ClientRectangle;
                //sd.Rectangle screen = engine.Control.RectangleToScreen(client);

                //-------------------------------
                //  only if inside the viewPort
                //-------------------------------
                //if (screen.Contains(currentMouseState.X, currentMouseState.Y))
                //{
                    //-------------------------------
                    //  if RMB pressed drag camera
                    //-------------------------------
                    if (currentMouseState.RightButton == ButtonState.Pressed && currentMouseState != originalMouseState)
                    {
                        float xDifference = currentMouseState.X - originalMouseState.X;
                        float yDifference = currentMouseState.Y - originalMouseState.Y;
                       
                        if (xDifference != 0)
                        {
                            cameraYaw += rotationPerSecond * xDifference * 0.05f;
                            yaw *= Quaternion.CreateFromAxisAngle(Vector3.Up, cameraYaw);
                        }
                        if (yDifference != 0)
                        {
                            cameraPitch += rotationPerSecond * yDifference * 0.05f;
                            pitch *= Quaternion.CreateFromAxisAngle(Vector3.Right, cameraPitch);
                        }
                        Quaternion finalRot = yaw * pitch;
                        camera.Pose.SetRotation(ref finalRot);
                        isDirty = true;
                    }
                    originalMouseState = currentMouseState;
                }
            //}

            Vector3 movementVector = Vector3.Zero;
            Vector3 upDown = Vector3.Zero;

            if (Input.KeyboardUpPressed)
                movementVector += Vector3.Forward;
            if (Input .KeyboardDownPressed )
                movementVector += Vector3.Backward;
            if (Input.KeyboardRightPressed )
                movementVector += Vector3.Right;
            if (Input.KeyboardLeftPressed )
                movementVector += Vector3.Left ;
            
            //if (keyState.IsKeyDown(input.GetKey("up")))
                //upDown += Vector3.Up;
           // if (keyState.IsKeyDown(input.GetKey("down")))
              //  upDown += Vector3.Down;

            //if (keyState.IsKeyDown(input.GetKey("run")))
           // {
            //    movementVector *= movementFactor;
             //   upDown *= movementFactor;
            //}
            if (movementVector.LengthSquared()!=0 || upDown.LengthSquared()!=0)
            {
                isDirty = true;
                Vector3 position = camera.Pose.Position + CameraSceneNode.GetMoveVector(camera.Pose.Rotation, movementVector, movementPerSecond) + upDown;
                camera.Pose.SetPosition(ref position);
            }
            if (isDirty)
            {
                camera.UpdateViewMatrix(true);
                isDirty = false;
                RaiseCameraMoved();
            }
        }

        private void RaiseCameraMoved()
        {
            if (CameraMoved != null)
            {                
                CameraMoved();
            }
        }
    }
}
